package com.example.demo.exception;

import com.example.demo.vo.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }

    @ExceptionHandler(BusinessException.class)
    public Response bizException(BusinessException e) {
        return Response.fail(e.getCode(), e.getMessage(), e.getData());
    }

    /**
     * 400 - Bad Request 参数类型错误
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Response handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        logger.error("参数类型错误", e);
        return Response.fail(HttpStatus.BAD_REQUEST, "参数类型错误");
    }

    /**
     * 400 - Bad Request 缺少请求参数
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Response handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        logger.error("缺少请求参数", e);
        return Response.fail(HttpStatus.BAD_REQUEST, "缺少请求参数");
    }

    /**
     * 400 - Bad Request 参数解析失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Response handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        logger.error("could not read json", e);

        return Response.fail(HttpStatus.BAD_REQUEST,
                "could not read json “" + e.getMessage().substring(0, e.getMessage().indexOf("\n")) + "”");
    }

    /**
     * 400 - Bad Request 参数验证失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Response handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error("parameter validation failure", e);

        String errorMsg = getMessage(e.getBindingResult());
        return Response.fail(HttpStatus.BAD_REQUEST, errorMsg);
    }

    /**
     * 400 - Bad Request 参数绑定失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Response handleBindException(BindException e) {
        logger.error("parameter binding failure", e);
        String errorMsg = getMessage(e.getBindingResult());
        return Response.fail(HttpStatus.BAD_REQUEST, errorMsg);
    }

    /**
     * 400 - Bad Request 参数验证失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public Response handleServiceException(ConstraintViolationException e) {
        logger.error("parameter validation failure", e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        String errorMsg = violation.getMessage();

        return Response.fail(HttpStatus.BAD_REQUEST, "parameter:" + errorMsg);
    }

    /**
     * 400 - Bad Request 参数验证失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public Response handleValidationException(ValidationException e) {
        logger.error("parameter validation failure", e);
        return Response.fail(HttpStatus.BAD_REQUEST, "validation exception");
    }

    /**
     * 405 - Method Not Allowed 不支持当前请求方法
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Response handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        logger.error("request method not supported", e);

        return Response.fail(HttpStatus.METHOD_NOT_ALLOWED, "request method not supported");
    }

    /**
     * 415 - Unsupported Media Type 不支持当前媒体类型
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Response handleHttpMediaTypeNotSupportedException(Exception e) {
        logger.error("content type not supported", e);

        return Response.fail(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "content type not supported");
    }

    @ExceptionHandler(IOException.class)
    public Response handleFileNotFoundException(IOException e) {
        logger.error("FileNotFoundException", e);
        return Response.fail(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
    }

    /**
     * 500 - Internal Server Error 通用异常
     */
    @ExceptionHandler(Exception.class)
    public Response handleException400(Exception e) {
        logger.error("other exceptions", e);
        return Response.fail(HttpStatus.INTERNAL_SERVER_ERROR, "请联系管理员！");
    }

    /**
     * 把错误的信息封装
     *
     * @param result
     * @return
     */
    private static String getMessage(BindingResult result) {
        List<FieldError> fieldErrorList = null;
        String errorMsg = null;
        if (result.hasErrors()) {
            fieldErrorList = result.getFieldErrors();
            errorMsg = fieldErrorList.stream()
                    .map(FieldError -> FieldError.getField() + ":" + FieldError.getDefaultMessage())
                    .collect(Collectors.joining("|")).replace("\"", "");
        }
        return errorMsg;
    }

}
